/*
 * CPEFinal1.c
 *
 * Created: 5/9/2015 9:16:00 PM
 *  Author: Clinton
 */ 

#define F_CPU 8000000UL
#define P_GAIN 0.175
#define I_GAIN 0.000020
#define D_GAIN 0.022
#define BAUDRATE 9600
#define	LCD_DPRTH  PORTD								//LCD Ports are divided into two sections
#define LCD_DPRTL PORTC 								//LCD Ports are divided into two sections
#define	LCD_DDRH  DDRD  								//Used to control the direction of LCD Ports
#define LCD_DDRL  DDRC 									//Used to control the direction of LCD Ports
#define	LCD_DPIN  PIND 									//Defines PIN for LCD
#define	LCD_CPRT  PORTB 								//Used as control port for LCD
#define	LCD_CDDR  DDRB 									//Used to control the direction of LCD port
#define	LCD_CPIN  PINB 									//Defines PIN for LCD controls
#define	LCD_RS  3 										//Register Select
#define	LCD_RW  4 										//Read/ Write Assert
#define	LCD_EN  5										//LCD Enable
#define BAUD_PRESCALER (F_CPU/(BAUDRATE*16UL) - 1 )		//Baud Prescaler

#include <avr/io.h>    		
#include <util/delay.h>		
#include <stdlib.h>
#include <stdio.h>
#include <avr/interrupt.h>

char buffer[8];											//used to convert rpm to string	
int counter = 0;										//Global counter; used in interrupts								
uint16_t freq = 0;										//Frequency of the motor								
long rpm = 0;											//Calculated RPM of motor
int t2ovf = 0;											//Counts the number of overflow instances in timer 2
int switchscreen = 0;									//Used to swithc the display mode on LCD

void USART_INIT(void)
{
	// This function will set the micro controller for serial communication with the PC
	// It sets the necessary baud rate registers, and prescaler value.
	UBRR0H = (uint8_t)(BAUD_PRESCALER >> 8);
	UBRR0L = (uint8_t)(BAUD_PRESCALER);
	UCSR0B = (1 << RXEN0) | (1 << TXEN0);
	UCSR0C = (3 << UCSZ00);
}
void USART_SEND(unsigned char data)
{
	// Function to send data from the ATmega328P to the terminal on the PC
	while(!(UCSR0A & (1 << UDRE0)));
	UDR0 = data;
}
unsigned char USART_RECEIVE(void)
{
	// This function receives data from the PC Terminal through
	// the USB cable.
	while(!(UCSR0A & (1<<RXC0)));
	return UDR0;
}
void lcdCommand( unsigned char cmnd )
{
	// This function is used to handle commands to control LCD 
  LCD_DPRTH = (cmnd>>4) + 0xF0;			
  LCD_DPRTL = cmnd & 0x0F;
  LCD_CPRT &= ~ (1<<LCD_RS);
  LCD_CPRT &= ~ (1<<LCD_RW);
  LCD_CPRT |= (1<<LCD_EN);	
  _delay_us(1);				
  LCD_CPRT &= ~ (1<<LCD_EN);
  _delay_us(100);			
}
void lcdData( unsigned char data )
{
	// This function is used to handle data to be displayed on the LCD
  LCD_DPRTH = (data>>4) + 0xF0;
  LCD_DPRTL = data & 0x0F;			
  LCD_CPRT |= (1<<LCD_RS);	
  LCD_CPRT &= ~ (1<<LCD_RW);
  LCD_CPRT |= (1<<LCD_EN);	
  _delay_us(1);				
  LCD_CPRT &= ~ (1<<LCD_EN);
  _delay_us(100);			
}
void lcd_init()
{
	// This function initalizes the LCD; essentially clears data before new data is
	// needed to be display
  LCD_DDRH = 0xFF;
  LCD_DDRL = 0x0F;
  LCD_CDDR = 0xFE;
 
  LCD_CPRT &=~(1<<LCD_EN);	
  _delay_us(2000);			
  lcdCommand(0x38);			
  lcdCommand(0x0E);			
  lcdCommand(0x01);			
  _delay_us(2000);			
  lcdCommand(0x06);			
}
void lcd_gotoxy(unsigned char x, unsigned char y)
{  
	//This function defines the starting address of the LCD
 unsigned char firstCharAdr[]={0x80,0xC0,0x94,0xD4};//table 12-5  
 lcdCommand(firstCharAdr[y-1] + x - 1);
 _delay_us(100);	
}
void lcd_print( char * str )
{
	//This function handles the strings that may be printed on the display
  unsigned char i = 0 ;
  while(str[i]!=0)
  {
    lcdData(str[i]);
    i++ ;
  }
}
void displayFreq()
{
	// This function displays the frequency and duty cycle
	int duty = OCR0A * 100 /255;
	itoa(freq,buffer, 10 );
	lcd_init();
	lcd_gotoxy(1,1);
	lcd_print(buffer);
	
	itoa(duty,buffer, 10 );
	
	lcd_gotoxy(13,1);
	lcd_print("freq");
	
	lcd_gotoxy(1,2);
	lcd_print(buffer);
	
	lcd_gotoxy(13,2);
	lcd_print("duty");
}
void displayRpm(int rpmin, int targrpm)
{
	// This function displays the rpm and target rpm 
	itoa(rpmin,buffer, 10 );
	lcd_init();
	lcd_gotoxy(1,1);
	lcd_print(buffer);
	
	lcd_gotoxy(14,1);
	lcd_print("rpm");
	
	itoa(targrpm,buffer, 10 );
	lcd_gotoxy(1,2);
	lcd_print(buffer);
	
	lcd_gotoxy(12,2);
	lcd_print("t.rpm");
}
int main(void) 
{	
	DDRB = 0xFE;									//Set portb as an output
	DDRD = 0xFF;									//Portb as output
	
	TCCR0A = 0x83;									//Timer 0 fast pwm mode
	TCCR0B = 0x03;									//Prescaler 64
	OCR0A = 255;									//100% duty cycle
	
	TCCR1A = 0x00;									//Timer 1 normal mode
	TCCR1B = 0xC3;									//Timer 1 prescaler = 64
	TIMSK1 = 0x22;									//Enable capture on input

	TCCR2A = 0x00;									//Timer 2 normal mode
	TCCR2B = 0x07;									//Prescaler = 1024
	TIMSK2 = 0x02;									//Enable OCR2A compare flag
	OCR2A = 255;
	ADCSRA = 0x87;									//Enable adc, prescaler = 128
	
	ADMUX = 0x47;									//5.0V External Reference, Left Adjust = off, using ADC7
	
	sei();											//Set Interupts
	
	int i = 0;										//Used for several for loops
	int targetRpm = 400;							//Used to set RPM
	
	int error = 0;									//setrpm - rpm
	int I_error = 0;								//sum of error
	int D_error = 0;								//previous error - error
	int output;										//Change of duty cycle required
	int previous_error	= 0;						//Previous error
	
	int avgRPM = 0;									//Average RPM
	int rpm10 = 0;									//Sum of 10 RPM
	int rpmCounter = 0;								//Counts 10 RPMs for the average rpm calculation									
	
	char rpmStr[4] = "rpm";							//Used to print to LCD
	char trpmStr[12] = "target rpm";				//Used to print to LCD
	
	USART_INIT();									//Initializes Serial Connection
		
	while(1){										//Starts main while loop
		ADCSRA |= (1<<ADSC);						//Start conversion
		while((ADCSRA&(1<<ADIF))==0);

		targetRpm = ADC/2.380 + 20;					//Calculate Set RPM from the potentiometer
		freq = 125000/(ICR1) -1;					//Calculates Frequency from 1/16 of a rotation
		rpm = freq * 0.197;							//Calculates RPM from Frequency
		error = targetRpm - rpm;					//Calculates error from set rpm - rpm
		I_error += (error);							//Calculates sum of errors
		D_error = (error - previous_error);			//Calculates the difference of past two errors
		output = (P_GAIN * error) + 
		(I_GAIN * I_error) + (D_GAIN * D_error);	//Output Calculations
		
		previous_error = error;						//Stores Previous Error
		
		if(OCR0A + output > 255)					//Checks for Output Overflow
		{
			OCR0A= 255;
		}
		
		else if(OCR0A + output < 0)					//Checks for Output underflow
		{
			OCR0A= 0;
		}
		else
			OCR0A += output;						//Adjusts Output
		
		//The following USART transmission code is used to plot the data in
		//Megunolink	
		USART_SEND('{');
		for(i = 0; i < 3;i++)
			USART_SEND(rpmStr[i]);
		USART_SEND(',');
		USART_SEND('T');
		USART_SEND(',');
		itoa(rpm, buffer, 10);
		i = 0;
		while(buffer[i] != '\0')
		{
			USART_SEND(buffer[i]);
			i++;
		}	
		USART_SEND('}');
		USART_SEND('{');
		for(i = 0; i < 12;i++)
			USART_SEND(trpmStr[i]);
		USART_SEND(',');
		USART_SEND('T');
		USART_SEND(',');
		itoa(targetRpm, buffer, 10);
		i = 0;
		while(buffer[i] != '\0')
		{
			USART_SEND(buffer[i]);
			i++;
		}
		USART_SEND('}');
		rpmCounter++;							//Increments counts of RPM
		rpm10 += rpm;							//Sums RPM
		avgRPM = rpm10/rpmCounter;				//Calculates Average RPM
		
		if(switchscreen == 0)					//Checks if it is time to switch the display mode
		{
			_delay_ms(333);
			displayRpm(avgRPM,targetRpm);		//Displays Average RPM and Target RPM
		}
		else
		{
			_delay_ms(333);
			displayFreq();						//Displays Frequency and Duty Cycle
		}
		
		if(rpmCounter == 10)					//Checks if 10 RPMs have been summed
		{
			rpm10 = 0;							//Resets RPM
			rpmCounter = 0;						//Resets counter for RPMS
		}	
	}
    return 0;
}
ISR(TIMER1_CAPT_vect)
{	
	TCNT1 = 0;									//Resets timer 1
}
ISR(TIMER2_COMPA_vect)
{
	//This function is used to set a 2 second switching time 
	//for the LCD output
	t2ovf++;									//Increments Counter for timer 2 overflow
	if(t2ovf == 61)
		{
			if(switchscreen == 0)
				switchscreen = 1;
			else
				switchscreen =0;
			t2ovf = 0;							//Resets overflow counter
		}
}
